﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;

namespace Mandelbrot
{
    /// <summary>
    /// Class for the generation and storage of an ordered list of colours.
    /// </summary>
    /// <remarks>
    /// Please note that this class is designed with the following
    /// assumptions (which are all safe within the current project):
    ///  - This palette will only be used for drawing fractals.  Therefore, the
    ///    colours generated may not differ too sharply, or it'll look ugly.
    ///  - 512 colours are required.  Having any less is a (fatal) error.
    /// </remarks>
    class ColourPalette
    {
        #region member variables
        // The class must guarantee that at least this many colours are generated.
        private static int PromisedNumberOfColours = 512;
        private IList<Int32> colours;
        #endregion

        #region properties
        public Int32 this[int i]
        {
            get
            {
                return this.colours[i];
            }
        }
        #endregion

        #region constructors
        /// <summary>
        /// Generate smooth transitions between four colours.
        /// </summary>
        public ColourPalette(Color first, Color second, Color third, Color fourth)
        {
            colours = new List<int>();
            this.populate(first, second, 128);
            this.populate(second, third, 128);
            this.populate(third, fourth, 128);
            this.populate(fourth, first, 128);
            Debug.Assert(this.colours.Count >= ColourPalette.PromisedNumberOfColours,
                "Not enough colours generated by ColourPalette.");
        }

        /// <summary>
        /// Add a belt of colours to the end of the current colours.
        /// </summary>
        /// <remarks>
        /// Put in constructors as it is only ever called from the constructor.
        /// </remarks>
        /// <param name="startColor">Colour the belt should begin at.</param>
        /// <param name="endColor">Colour the belt should end at.</param>
        /// <param name="colourBands">Number of colours to generate.</param>
        private void populate(Color startColor, Color endColor, int colourBands)
        {
            int r, g, b;
            int deltaR = endColor.R - startColor.R;
            int deltaG = endColor.G - startColor.G;
            int deltaB = endColor.B - startColor.B;
            for (int i = 0; i < colourBands; i++)
            {
                r = startColor.R + calcShade(i, colourBands, deltaR);
                g = startColor.G + calcShade(i, colourBands, deltaG);
                b = startColor.B + calcShade(i, colourBands, deltaB);
                this.colours.Add(ColourPalette.int32FromRGB(r, g, b));
            }
        }
        #endregion

        #region private methods
        /// <summary>
        /// Calculate the shade of a single belt.
        /// </summary>
        private static int calcShade(int i, int colourBands, int deltaR)
        {
            return (int)(deltaR * (double)(i) / colourBands);
        }

        /// <summary>
        /// Merge three ints (red, green, blue) into a single int (argb).
        /// </summary>
        /// <param name="r">Red</param>
        /// <param name="g">Green</param>
        /// <param name="b">Blue</param>
        /// <returns></returns>
        private static Int32 int32FromRGB(int r, int g, int b)
        {
            return ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
        }
        #endregion
    }
}
